/*===============================================================================================
 codec_game.dll
 Ver. 1.2
 by Andreas Argirakis 2008.
 FMOD input plugin
 Visit http://andreas.blazer.nu for latest version
 Built with Game_Music_Emu, http://www.slack.net/~ant/libs/audio.html

===============================================================================================*/
#include "Music_Emu.h"
#include <blargg_endian.h>
#include <stdio.h>
#include <iostream>
#include "fmod_errors.h"
#include <windows.h>
extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

void ERRCHECK(FMOD_RESULT result)
{
    if (result != FMOD_OK)
    {
//        LogFile->Print("FMOD error! (%d) %s\n", result, FMOD_ErrorString(result));
        exit(-1);
    }
}
FMOD_RESULT handle_error( const char* str )
{
	if ( str )
	{
//		LogFile->Print( "Error: %s\n", str );
		return FMOD_ERR_INTERNAL;
	}
	else
		return FMOD_OK;
}
FMOD_RESULT F_CALLBACK open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo);
FMOD_RESULT F_CALLBACK close(FMOD_CODEC_STATE *codec);
FMOD_RESULT F_CALLBACK read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read);
FMOD_RESULT F_CALLBACK setposition(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype);
FMOD_RESULT F_CALLBACK getlength(FMOD_CODEC_STATE *codec, unsigned int *length, FMOD_TIMEUNIT lengthtype);
FMOD_RESULT F_CALLBACK getposition(FMOD_CODEC_STATE *codec, unsigned int *position, FMOD_TIMEUNIT postype);
FMOD_CODEC_DESCRIPTION gamecodec =
{
    "FMOD Game Music Player Plugin",			// Name.
    0x00012000,                         // Version 0xAAAABBBB   A = major, B = minor.
    1,                                  // Force everything using this codec to be a stream
    FMOD_TIMEUNIT_MS|FMOD_TIMEUNIT_MODPATTERN,					// The time format we would like to accept into setposition/getposition.
    &open,                           // Open callback.
    &close,                          // Close callback.
    &read,                           // Read callback.
    &getlength,                      // Getlength callback.  (If not specified FMOD return the length in FMOD_TIMEUNIT_PCM, FMOD_TIMEUNIT_MS or FMOD_TIMEUNIT_PCMBYTES units based on the lengthpcm member of the FMOD_CODEC structure).
    &setposition,                    // Setposition callback.
    &getposition,                    // Getposition callback. (only used for timeunit types that are not FMOD_TIMEUNIT_PCM, FMOD_TIMEUNIT_MS and FMOD_TIMEUNIT_PCMBYTES).
    0                                // Sound create callback (don't need it)
};
class gameplugin
{
	FMOD_CODEC_STATE *_codec;

	public:
	gameplugin(FMOD_CODEC_STATE *codec)
	{
		_codec = codec;
//		LogFile = new CLogFile("gameemu.log");
		memset(&gpwaveformat, 0, sizeof(gpwaveformat));
	}

	~gameplugin()
	{
		//delete some stuff
		gme_delete( emu );
	}
	Music_Emu* emu;
	FMOD_CODEC_WAVEFORMAT gpwaveformat;
	track_info_t info;

};

/*
    FMODGetCodecDescription is mandatory for every fmod plugin.  This is the symbol the registerplugin function searches for.
    Must be declared with F_API to make it export as stdcall.
    MUST BE EXTERN'ED AS C!  C++ functions will be mangled incorrectly and not load in fmod.
*/
#ifdef __cplusplus
extern "C" {
#endif

FMOD_CODEC_DESCRIPTION* F_API FMODGetCodecDescription()
{
    return &gamecodec;
}

#ifdef __cplusplus
}
#endif



FMOD_RESULT F_CALLBACK open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo)
{


	FMOD_RESULT       result;
	gameplugin *gp = new gameplugin(codec);

	//check what music format the file is
	codec->fileseek(codec->filehandle, 0, 0);
    char header[4] = "";
    codec->fileread(codec->filehandle, header, 4, 0, 0);

	switch ( get_be32( header ) )
	{
	  case BLARGG_4CHAR('N','E','S','M'):
			gp->emu = gme_new_emu(gme_nsf_type, 44100);
			break;
	  case BLARGG_4CHAR('N','S','F','E'):
			gp->emu = gme_new_emu(gme_nsfe_type, 44100);
			break;
	  case BLARGG_4CHAR('S','N','E','S'):
			gp->emu = gme_new_emu(gme_spc_type, 44100);
			break;
	  case BLARGG_4CHAR('Z','X','A','Y'):
			gp->emu = gme_new_emu(gme_ay_type, 44100);
			break;
	  case BLARGG_4CHAR('G','B','S',0x01):
			gp->emu = gme_new_emu(gme_gbs_type, 44100);
			break;
	  case BLARGG_4CHAR('G','Y','M','X'):
			gp->emu = gme_new_emu(gme_gym_type, 44100);
			break;
	  case BLARGG_4CHAR('H','E','S','M'):
			gp->emu = gme_new_emu(gme_hes_type, 44100);
			break;
	  case BLARGG_4CHAR('K','S','C','C'):
	  case BLARGG_4CHAR('K','S','S','X'):
			gp->emu = gme_new_emu(gme_kss_type, 44100);
			break;
	  case BLARGG_4CHAR('S','A','P',0x0D):
			gp->emu = gme_new_emu(gme_sap_type, 44100);
			break;
	  case BLARGG_4CHAR('V','g','m',' '):
			gp->emu = gme_new_emu(gme_vgm_type, 44100);
			break;
	  default:
			return FMOD_ERR_FORMAT;
	}

	unsigned int bytesread;

	/* Allocate space for buffer. */
	signed short* myBuffer = new signed short[codec->filesize];

	//rewind file pointer
	result = codec->fileseek(codec->filehandle,0,(signed short*)myBuffer);
	ERRCHECK(result);

	//read whole file to memory
	result = codec->fileread(codec->filehandle,(signed short*)myBuffer,codec->filesize,&bytesread,0);
	ERRCHECK(result);

	handle_error(gme_load_data( gp->emu, myBuffer, codec->filesize)) ;
	delete [] myBuffer;

	int track = 0;

	handle_error(gme_track_info( gp->emu, &gp->info, track));

	/* Get and print main info for track */

	/*printf( "System   : %s\n", info.system );
	printf( "Game     : %s\n", info.game );
	printf( "Author   : %s\n", info.author );
	printf( "Copyright: %s\n", info.copyright );
	printf( "Comment  : %s\n", info.comment );
	printf( "Dumper   : %s\n", info.dumper );
	printf( "Tracks   : %d\n", (int) info.track_count );
	printf( "\n" );
	printf( "Track    : %d\n", (int) track + 1 );
	printf( "Name     : %s\n", info.song );
	printf( "Length   : %ld:%02ld",
			(long) info.length / 1000 / 60, (long) info.length / 1000 % 60 );
	if ( info.loop_length != 0 )
		printf( " (endless)" );*/

	gp->gpwaveformat.format       = FMOD_SOUND_FORMAT_PCM16;
	gp->gpwaveformat.channels     = 2;
	gp->gpwaveformat.frequency    = 44100;
	gp->gpwaveformat.blockalign   = (16 >> 3) * gp->gpwaveformat.channels;
	gp->gpwaveformat.lengthpcm = 0xffffffff;

	// If file doesn't have overall length, it might have intro and loop lengths
    if ( gp->info.length <= 0 )
        gp->info.length = gp->info.intro_length + gp->info.loop_length * 2;

	if(gp->info.length>0)
	{
		gp->gpwaveformat.lengthpcm    = gp->info.length/1000*gp->gpwaveformat.frequency;
	}

	codec->waveformat   = &(gp->gpwaveformat);
	codec->numsubsounds = 0;                    /* number of 'subsounds' in this sound.  For most codecs this is 0, only multi sound codecs such as FSB or CDDA have subsounds. */
	codec->plugindata   = gp;                    /* user data value */

	handle_error( gme_start_track( gp->emu, track ) );
	return FMOD_OK;

}

FMOD_RESULT F_CALLBACK close(FMOD_CODEC_STATE *codec)
{
	//gameplugin* gp = (gameplugin*)codec->plugindata;
	delete (gameplugin*)codec->plugindata;
	//delete LogFile;
    return FMOD_OK;
}

FMOD_RESULT F_CALLBACK read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read)
{
	gameplugin* gp = (gameplugin*)codec->plugindata;
	gp->emu->play(size/2,(signed short*)buffer);

	*read=size;

    return gme_track_ended( gp->emu ) ? FMOD_ERR_INVALID_PARAM : FMOD_OK;
}

FMOD_RESULT F_CALLBACK setposition(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype)
{
	gameplugin* gp = (gameplugin*)codec->plugindata;

	if(postype==FMOD_TIMEUNIT_MS)
	{
		return handle_error(gme_seek(gp->emu,position));
	}
	else if(postype==FMOD_TIMEUNIT_MODPATTERN)
	{
		if(position<1) position = 1;
		return handle_error( gme_start_track( gp->emu, position ) );
	}

    return FMOD_OK;
}
FMOD_RESULT F_CALLBACK getlength(FMOD_CODEC_STATE *codec, unsigned int *length, FMOD_TIMEUNIT lengthtype)
{
	gameplugin* gp = (gameplugin*)codec->plugindata;

	if(lengthtype==FMOD_TIMEUNIT_MS)
	{
		*length = gp->gpwaveformat.lengthpcm;
	}
	else if(lengthtype==FMOD_TIMEUNIT_MODPATTERN)
	{
		*length = gp->info.track_count;
	}
    return FMOD_OK;
}
FMOD_RESULT F_CALLBACK getposition(FMOD_CODEC_STATE *  codec, unsigned int *position, FMOD_TIMEUNIT postype)
{
	gameplugin* gp = (gameplugin*)codec->plugindata;

	if(postype==FMOD_TIMEUNIT_MS)
	{
		*position = 0;
	}
	else if(postype==FMOD_TIMEUNIT_MODPATTERN)
	{
		*position = gp->emu->current_track();
	}
    return FMOD_OK;
}

#ifdef __cplusplus
extern "C" {
#endif

int luaopen_audio_retro(lua_State *L)
{
    lua_getglobal(L, "require");
    lua_pushliteral(L, "audio");
    lua_call(L,1,0);

    lua_getfield(L, LUA_REGISTRYINDEX, "FMOD_SYSTEM");
    FMOD_SYSTEM* fmodSystem = (FMOD_SYSTEM*)lua_touserdata(L,-1);
    lua_pop(L,1);

    FMOD_System_CreateCodec(fmodSystem, &gamecodec);

    lua_pushboolean(L, true);
    return 1;
}

#ifdef __cplusplus
}
#endif

